#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <string>
#include <iomanip>
#include <locale>
#include <sstream>
#include <assert.h>
#include <vector>
#include <map>
using namespace std;

#define MAX_DAYS  3000
#define MAX_STOCKS 500
#define MAX_FAILURES 10

class stock_stat {
    public:
        // From the table
        double open, close, high, low, fsto_k, ssto_k, ssto_d, rank;

        //derived
        double diver, hh, hh30, ll30, ll, sma50, sma200, ema, ema12, ema26, macd, ema9_macd, rsi, max, min;

        stock_stat(){
           open = close = high = low = ssto_k = ssto_d = rank = 0.0; 
           fsto_k = hh = ll = hh30 = ll30 = ema = ema12 = ema26 = macd = ema9_macd = 0.0;
           diver = 0;
           max = min = 0;
        }

        ~stock_stat()
        { }
};

class stock{
    public:
        stock_stat sstat[MAX_DAYS];
        // stock name
        string name;


        // calculate relative strength index
        void rsi(int days)
        {
            int i, j;
            for (j = 1; j < MAX_DAYS - days - 2; j++) {
                // variables to keep the sum of up and down days
                double up_days_diff = 0, down_days_diff = 0;
                for (i = j; i < days + j ; i++) {
                    if (sstat[i].close < sstat[i + 1].close)
                        down_days_diff += sstat[i + 1].close - sstat[i].close;
                    else
                        up_days_diff += sstat[i].close - sstat[i + 1].close;
                }

                double rs = up_days_diff / down_days_diff;

                sstat[j].rsi = 100 - 100 / (1 + rs);    


            }
        }

        void ssto(int days)
        {
            int i;

            // First calculate the highest highs and lowest lows
            for (i = MAX_DAYS - days - 1; i > 0; i--) {
                sstat[i].hh = find_highest_high(i, days);
                sstat[i].hh30 = find_highest_high_prior(i, 30);
                sstat[i].ll = find_lowest_low(i, days);
                sstat[i].ll30 = find_lowest_low_prior(i, 30);
                sstat[i].fsto_k = ((sstat[i].close - sstat[i].ll) /
                        (sstat[i].hh - sstat[i].ll)) * 100; 
               
                // ugly but prevents multiple loops 
                if (i < MAX_DAYS - days - 4)
                    sstat[i].ssto_k = (sstat[i + 1].fsto_k + sstat[i +
                            2].fsto_k + sstat[i + 3].fsto_k) / 3;

                if (i < MAX_DAYS - days - 7)
                    sstat[i].ssto_d = (sstat[i + 1].ssto_k + sstat[i +
                            2].ssto_k + sstat[i + 3].ssto_k) / 3;
                
            }
        }

        void calc_macd()
        {
            sstat[MAX_DAYS - 1].ema12 = sstat[MAX_DAYS - 1].close;
            sstat[MAX_DAYS - 1].ema26 = sstat[MAX_DAYS - 1].close;

            int i;
            double alpha12 = .15;
            double alpha26 = .075;
            double alpha9 = .2;
            for(i = MAX_DAYS - 2; i > 0; i--) {
                sstat[i].ema12 = 0.15 * sstat[i].close + 0.85 * sstat[i +
                    1].ema12;
                sstat[i].ema26 = 0.075 * sstat[i].close + .925 * sstat[i +
                    1].ema26;
                sstat[i].macd = sstat[i].ema12 - sstat[i].ema26;
                sstat[i].ema9_macd = 0.2 * sstat[i].macd + 0.8 * sstat[i +
                    1].ema9_macd;
                sstat[i].diver = sstat[i].macd - sstat[i].ema9_macd;
                //cout << "macd " << sstat[i].macd << " " << sstat[i].ema9_macd << endl ;
            }
        }

        void ema(int days)
        {
            int i;
            if (!days)
                return;
            
            // boundary condition
            sstat[MAX_DAYS - 1].ema = sstat[MAX_DAYS - 1].close;

            double alpha = 2 / (days + 1);
            for(i = MAX_DAYS - 2; i >=0; i--)
                sstat[i].ema = alpha * sstat[i].close + (1 - alpha) *
                    sstat[i + 1].ema;


        }

        void calc_max_min_52(int days) {
            int i;
            if (!days)
                    return;

            double run_max, run_min;
            run_max = sstat[1].max = sstat[1].close;
            run_min = sstat[1].min = sstat[1].close;

            for (i = 2; i < MAX_DAYS - 2; i++) {
               if (sstat[i].close > run_max)
                  run_max = sstat[i].close; 

               if (sstat[i].close < run_min)
                   run_min = sstat[i].close;

               sstat[i].max = run_max;
               sstat[i].min = run_min;

            }
            sstat[1].max = run_max;
            sstat[1].min = run_min;

        
        }
        
        void print_stats()
        {
            cout << name << "\t"<< sstat[1].ssto_k << "\t " <<
                sstat[1].ssto_d << "\t " << sstat[1].ema << "\t" <<
                sstat[1].macd << "\t" << sstat[1].ema9_macd << 
                "\t" << sstat[1].rsi << endl;

        }
    private:
        double find_highest_high(int index, int days)
        {
            int i;
            double local_hh = sstat[index].high;
            for (i = index + 1; i < index + days; i++)
                if (sstat[i].high > local_hh) {
                    local_hh = sstat[i].high;
                }
            return local_hh;
        }
        
        double find_lowest_low(int index, int days)
        {
            int i;
            double local_ll = sstat[index].low;
            for (i = index + 1; i < index + days; i++)
                if (sstat[i].low < local_ll) {
                    local_ll = sstat[i].low;
                }
            return local_ll;
        }
        
        double find_highest_high_prior(int index, int days)
        {
            int i;
            double local_hh = sstat[index].high;
            for (i = index; i >= 2,  i > index - days; i--)
                if (sstat[i].high > local_hh) {
                    local_hh = sstat[i].high;
                }
            return local_hh;
        }
        
        double find_lowest_low_prior(int index, int days)
        {
            int i;
            double local_ll = sstat[index].low;
            for (i = index; i >= 2, i >  index - days; i--)
                if (sstat[i].low < local_ll) {
                    local_ll = sstat[i].low;
                }
            return local_ll;
        }
};

stock *st;

// remove duplicates from the stock file list

int remove_duplicates_and_sort()
{ 
    string a = "cp stock_list.txt stock_list_tmp.txt";
    const char *d = a.c_str();

    system(d);

    ifstream inp_file;
    inp_file.open("stock_list_tmp.txt");

    // output file for processed stocks
    ofstream output_file;
    output_file.open("stock_list.txt");

    map <string, int> hash_t;
    string stock_name_line;
    while(inp_file.good()) {
        getline(inp_file, stock_name_line);
        if (!hash_t[stock_name_line]) {
            output_file << stock_name_line << endl;
            hash_t[stock_name_line] = 1;
        }
    }
   
    a = "rm -f stock_list_tmp.txt && sort stock_list.txt > stock_list_tmp.txt && mv stock_list_tmp.txt stock_list.txt";
    d = a.c_str();

    system(d);

    output_file.close();
    inp_file.close();
    return 0;
}

int read_stock_data_from_web()
{
    ifstream stocks_name_file;
    stocks_name_file.open("stock_list.txt");
    string stock_name_line;

    st = new stock[MAX_STOCKS];
    int st_count = 0;
    int failure_count = 0, failure_in_reading = 0;

    while(stocks_name_file.good()) {
        getline(stocks_name_file, stock_name_line);
        do {    

            string a = "\"" + stock_name_line +"\"";
            string c = "curl \"http://ichart.finance.yahoo.com/table.csv?d=12&e=31&f=2014&g=d&a=2&b=2&c=2002&ignore=.csv&s="+a+"\" | tee ./hist_data/"+a+".csv";
            const char *d = c.c_str();

            system(d);

            //Now make sense out of the data 
            ifstream myfile;

            a = "./hist_data/"+stock_name_line+".csv";
            const char *c_file = a.c_str();
            myfile.open(c_file);

            // Count the number of line

            int line_count = 0;
            // Run this while there are elements in the file
            string line;

            // call getline twice, because first line has no numerical value
            getline(myfile, line);
            getline(myfile, line);

            // Rotating position for the occurence of comma
            size_t found, loop_found;
            double result;

            // keep track of the attribute count
            int attr_count = 0;
            // Ignore the first attribute
            found = line.find_first_of(",");
            string num = line.substr(0, found);

            ++attr_count;
            // Convert the string to a number
            loop_found = line.find_first_of(",", found + 1);
            num = line.substr(found + 1, loop_found);
            istringstream convert(num);
            if (!(convert >> result)) {
                failure_in_reading = 1;
                ++failure_count;
                if (failure_count == MAX_FAILURES) {
                    failure_in_reading = 0;
                    failure_count = 0;
                }
            }
            else {
                failure_in_reading = 0;
                failure_count = 0;
            }

            ++line_count;
            // Close my file
            myfile.close(); 
        } while (failure_in_reading);
    }
    stocks_name_file.close();
    return 0;

}

void generate_buy_and_sell_signals(stock *st)
{
    int i;

    cout << "Call Option Signals" << endl;
    // First Generate the Call options
    for (i = 0; i < MAX_STOCKS; i++) {
        if (st[i].sstat[1].open) {
            if (st[i].sstat[1].rsi < 30) {
                if (st[i].sstat[1].ssto_k < 20) {
                    if (st[i].sstat[1].diver > st[i].sstat[2].diver &&
                            st[i].sstat[2].diver > st[i].sstat[3].diver) { 
                        // This is rank 1
                        st[i].sstat[1].rank = 1;

                    }
                    else {
                        st[i].sstat[1].rank = 2;
                        // This is rank 2
                    }     
                }
                // rank 3
                else {
                        st[i].sstat[1].rank = 3;
                }
                // rank 4

#if 1
                if (st[i].sstat[1].rank <= 3) {
                    cout << st[i].name << "\t";
                    cout << st[i].sstat[1].rank << "\t";
                    cout << st[i].sstat[1].rsi << "\t";
                    cout << st[i].sstat[1].max << "\t";
                    cout << st[i].sstat[1].min << "\t";
                    cout << st[i].sstat[1].close << "\t";
                    cout << endl;
                }
#endif
            }
            else { // rsi 35
                // Nothing
                st[i].sstat[1].rank = 5;
            }
        }
    }
#if 0
    cout << "\n\nPut Option Signals" << endl;
    // Now Generate the Put options
    for (i = 0; i < MAX_STOCKS; i++) {
        if (st[i].sstat[1].open) {
            if (st[i].sstat[1].rsi > 97) {
                cout << st[i].name << " ";
                st[i].sstat[1].rank = 1;
                cout << st[i].sstat[1].rank <<  endl;
            }
            else if (st[i].sstat[1].rsi > 85) {
                st[i].sstat[1].rank = 2;
            }     
            else if (st[i].sstat[1].rsi > 75) {
                st[i].sstat[1].rank = 3;
            }     

        }
    }
#endif
}

int main(int argc, char **argv)
{
    st = new stock[MAX_STOCKS];
    int st_count = 0;

    assert(argc == 2);
    
	// remove the duplicates and sort the stock list
    remove_duplicates_and_sort();

    // read stock data from web
    if (atoi(argv[1]) == 1)
        read_stock_data_from_web();

    ifstream stocks_name_file;
    stocks_name_file.open("stock_list.txt");
    string stock_name_line;

    while(stocks_name_file.good()) {
        getline(stocks_name_file, stock_name_line);
        string a = "\"" + stock_name_line +"\"";
        string c = "curl \"http://ichart.finance.yahoo.com/table.csv?d=12&e=31&f=2014&g=d&a=2&b=2&c=2012&ignore=.csv&s="+a+"\" | tee ./hist_data/"+a+".csv";
        const char *d = c.c_str();

        // Save the name of the stock
        st[st_count].name = a;

        //Now make sense out of the data 
        ifstream myfile;

        a = "./hist_data/"+stock_name_line+".csv";
        const char *c_file = a.c_str();
        myfile.open(c_file);

        // Count the number of line

        int line_count = 0;
        // Run this while there are elements in the file
        while (myfile.good() && line_count < MAX_DAYS) {
            string line;
            getline(myfile, line);

            // Rotating position for the occurence of comma
            size_t found, loop_found;
            double result;

            // keep track of the attribute count
            int attr_count = 0;
            // Ignore the first attribute
            found = line.find_first_of(",");
            string num = line.substr(0, found);

            ++attr_count;
            // Convert the string to a number
            while(attr_count < 5) {
                loop_found = line.find_first_of(",", found + 1);
                num = line.substr(found + 1, loop_found);
                istringstream convert(num);
                if (!(convert >> result)) {
                    result = 0;
                }

                switch(attr_count) {
                case 1:
                    st[st_count].sstat[line_count].open = result;
                    break;
                case 2:
                    st[st_count].sstat[line_count].high = result;
                    break;
                case 3:
                    st[st_count].sstat[line_count].low = result;
                    break;
                case 4: 
                    st[st_count].sstat[line_count].close = result;
                    break;
                }
                found = loop_found;
                ++attr_count;
            }
            ++line_count;
        }
        // Close my file
        myfile.close(); 

        int i = st_count;

        string train_file, test_file;
        const char *train_f, *test_f;
        // Calculate and store the statistics
        if (st[i].sstat[1].open) {
            st[i].ssto(14);
            st[i].ema(22);
            st[i].calc_macd();
            st[i].rsi(10);
            st[i].calc_max_min_52(MAX_DAYS);
            train_file = "./model_data_train/"+stock_name_line+".csv";
            train_f = train_file.c_str();
            test_file = "./model_data_test/"+stock_name_line+".csv";
            test_f = test_file.c_str();

            ofstream w_train, w_test;
            w_train.open(train_f);
            w_test.open(test_f);

            int classvar;
            if (st[i].sstat[1].hh30  > 1.05 *st[i].sstat[1].close && 
                    st[i].sstat[1].ll30 > 0.95 * st[i].sstat[1].close) 
                classvar = 1;
            else
                classvar = -1;
            w_test << classvar << " 1:" << st[i].sstat[1].ssto_k << " 2:" <<
                st[i].sstat[1].ssto_d << " 3:" << st[i].sstat[1].ema << " 4:" <<
                st[i].sstat[1].macd << " 5:" << st[i].sstat[1].ema9_macd <<
                " 6:" << st[i].sstat[1].rsi << endl;

            for (int j = 2; j < MAX_DAYS - 30; j++) {
                if (st[i].sstat[j].hh30  > 1.05 *st[i].sstat[j].close && 
                        st[i].sstat[j].ll30 > 0.9 * st[i].sstat[j].close && st[i].sstat[j].rsi <= 30) 
                    classvar = 1;
                else
                    classvar = -1;

                if(st[i].sstat[j].ssto_k == st[i].sstat[j].ssto_k
                        && st[i].sstat[j].ssto_d == st[i].sstat[j].ssto_d
                        && st[i].sstat[j].ema == st[i].sstat[j].ema
                        && st[i].sstat[j].macd == st[i].sstat[j].macd
                        && st[i].sstat[j].rsi == st[i].sstat[j].rsi)
                    w_train << classvar << " 1:" << st[i].sstat[j].ssto_k << " 2:" <<
                        st[i].sstat[j].ssto_d << " 3:" << st[i].sstat[j].ema << " 4:" <<
                        st[i].sstat[j].macd << " 5:" << st[i].sstat[j].ema9_macd <<
                        " 6:" << st[i].sstat[j].rsi <<  endl;
            }
            w_train.close();
            w_test.close();


        }
        ++st_count;

    }

    // Real Workhorse
    generate_buy_and_sell_signals(st);

    // Now do the training
    stocks_name_file.close();

}
